package com.giggle.mybatis.algorithm;

import com.giggle.mybatis.algorithm.base.Hashing;
import com.giggle.mybatis.algorithm.base.MurmurHash;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author guozichen
 * @ClassName:
 * @Description: (hash分表)
 * @date 2020/8/31 15:56
 */
public class HashingTable {

    private static final int DUMMY_NODE_NUM = 3000;
    private static final Hashing MURMUR_HASH = new MurmurHash();
    private static ConcurrentHashMap<String, Object> lockMap = new ConcurrentHashMap(100);
    private static final Map<String, TreeMap<Long, String>> resources = new LinkedHashMap();

    public HashingTable() {
    }

    private static TreeMap<Long, String> buildHashingNodes(String tableName, int subTableCount) {
        TreeMap<Long, String> nodes = null;
        List<String> subTableNames = new ArrayList();

        int i;
        for(i = 1; i < subTableCount + 1; ++i) {
            subTableNames.add(tableName.concat("_") + i);
        }

        nodes = new TreeMap();

        for(i = 0; i != subTableNames.size(); ++i) {
            String subTableName = (String)subTableNames.get(i);

            for(int n = 0; n < 3000; ++n) {
                nodes.put(MURMUR_HASH.hash("hashing" + i + "-table-" + n), subTableName);
            }
        }

        resources.put(tableName, nodes);
        return nodes;
    }

    public static String getSplitTableName(String tableName, Object splitKeyVal, Integer subTableCount) {
        if (splitKeyVal != null && !StringUtils.isBlank(splitKeyVal.toString())) {
            String hashTableName = null;
            TreeMap<Long, String> nodes = (TreeMap)resources.get(tableName);
            if (nodes == null) {
                Object lockObj = getLockObj(tableName);
                synchronized(lockObj) {
                    nodes = buildHashingNodes(tableName, subTableCount);
                }
            }

            SortedMap<Long, String> tail = nodes.tailMap(MURMUR_HASH.hash(splitKeyVal.toString()));
            if (tail.isEmpty()) {
                hashTableName = (String)nodes.get(nodes.firstKey());
            } else {
                hashTableName = (String)tail.get(tail.firstKey());
            }

            return hashTableName;
        } else {
            throw new RuntimeException("split key value is not null!");
        }
    }

    private static Object getLockObj(String tableName) {
        Object lockObj = lockMap.get(tableName);
        if (lockObj == null) {
            lockMap.putIfAbsent(tableName, new Byte("1"));
        }

        return lockMap.get(tableName);
    }
}
